Posts

How to use CDS with Spring Boot applications

Jun 6, 2024
Catherine Edelveis
21.9

In the previous article, Dmitry described ways to reduce Java application startup time. Here, I offer you to try out one of them, Class Data Sharing (CDS). It provides a good improvement in startup, not as drastic as with CRaC or Native Image, but it doesn’t necessitate code refactoring. And since Spring Boot 3.3 supports CDS, and BellSoft provides binaries and ready container images with Liberica JDK and CDS, starting with this feature couldn’t be easier!

What is Class Data Sharing (CDS)

Class Data Sharing (CDS) is a JVM feature aimed at reducing the startup and memory footprint of multiple JVM instances running on the same host. The feature loads a default set of classes from the system Java Archive (JAR) file and stores this data in a file, which is then available as read-only metadata to multiple JVM processes.

CDS was first introduced in JDK 5 and received numerous enhancements since then. For instance, JEP 310 in OpenJDK 10 extended on the CDS feature and introduced Application Class Data Sharing (AppCDS), which enables loading application classes into the archive as well. AppCDS is further improved with JEP 350 in OpenJDK 13 that allows for including all loaded application classes and library classes not present in the default CDS archive upon the application exit. Dynamic CDS eliminated the need to do trial runs to create a class list for the application.

So when we say below that we create and use a CDS archive with a Spring Boot application, we actually work with AppCDS.

How to use CDS with Spring Boot

Spring Boot 3.3 offers a convenient way to create a CDS archive. All you need is to specify two options upon application start:

  • -XX:ArchiveClassesAtExit=application.jsa to create an archive of classes and
  • -Dspring.context.exit=onRefresh to start and immediately exit the application after non-lazy beans have been instantiated and InitializingBean#afterPropertiesSet callbacks have been invoked.

Note that to run the application with the CDS archive, you must use the same JVM utilized for creating the archive. In addition, the classpath must be the same. If you don’t specify the classpath option, it will consist of the current directory.

Create and use the CDS archive on a local machine

For this tutorial, I will use the Spring Petclinic demo application. You can use any other project, just make sure that Spring Boot version is 3.3. As for the Java runtime, we will use Liberica JDK recommended by the Spring team. Download Liberica JDK 21 for your platform directly from the website or choose any other installation method described there.

Alright, we’re all set. Let’s first create a jar file of our application with:

mvn -Dmaven.test.skip=true clean package

But we are not going to use an executable jar. Running CDS directly on an executable jar in production is not recommended because running it introduces certain overhead. So, we will take advantage of an exploded jar, which is more efficient and is recommended to be used with CDS by Spring. The command for creating the CDS archive is as follows:

java -Djarmode=tools -jar target/spring-petclinic-3.3.0-SNAPSHOT.jar extract
java -XX:ArchiveClassesAtExit=./application.jsa -Dspring.context.exit=onRefresh -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

Note that if you use another JDK, you may get a warning similar to

-XX:ArchiveClassesAtExit is unsupported when the base CDS archive is not loaded. Run with -Xlog:cds for more info.

This means that you will have to create a base image first with -Xshare:dump.

We can now use the created archive to launch our application:

java -XX:SharedArchiveFile=application.jsa -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

That’s it, two commands only and your application uses the CDS archive!

Peeking under the hood of CDS

In the previous section, we let Spring Boot and JDK do their magic. But I want to lift the veil over the process of creating the CDS archive and see how many and what classes get dumped there.

That being said, modify the command for creating the archive and add logging to analyze the results of CDS implementation later:

java -XX:ArchiveClassesAtExit=application.jsa -Xlog:cds=debug:file=log/cds.log -Dspring.context.exit=onRefresh -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

The -Xlog:cds=debug:file=log/cds.log option will log the process of CDS archive creation.

Let’s modify the command for launching the application with the archive in a similar fashion. We can add logging to see whether the archive was actually used upon application startup (-Xlog:class+path=debug) and count the number of loaded classes (-Xlog:class+load=info):

java -XX:SharedArchiveFile=application.jsa -Xlog:class+load=info:file=log/class-load.log -Xlog:class+path=debug:file=log/class-path.log -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

Great! After the application has started, exit it and check the logs.

The cds.log file lists the information about the skipped classes, i.e., classes that didn’t make it into the archive for some reason. For instance:

[4.118s][warning][cds] Skipping net/bytebuddy/dynamic/ClassFileLocator$Resolution$Explicit: Old class has been linked
[4.118s][debug  ][cds] Skipping org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer$$Lambda+0x000000d0014722b0: Hidden class
[4.118s][warning][cds] Skipping net/bytebuddy/dynamic/DynamicType$Builder$MethodDefinition$ParameterDefinition$Simple: interface net/bytebuddy/dynamic/DynamicType$Builder$MethodDefinition$ExceptionDefinition is excluded
[4.118s][debug  ][cds] Skipping org/springframework/data/jpa/repository/query/JpaQueryParserSupport$ParseState$$Lambda+0x000000d001966cf8: Hidden class
[4.118s][debug  ][cds] Skipping java/lang/invoke/LambdaForm$DMH+0x000000d001b80c00: Hidden class
[4.118s][debug  ][cds] Skipping java/lang/invoke/LambdaForm$DMH+0x000000d001015c00: Hidden class
[4.118s][debug  ][cds] Skipping java/lang/invoke/LambdaForm$DMH+0x000000d001b4a000: Hidden class

These are

  • Hidden classes, which were introduced in JDK 15 with JEP 371 and represent classes that cannot be used directly by the bytecode of other classes. These classes are not suitable for the CDS archive due to their dynamic nature;
  • Old classes, which are classes from older Java versions. They can typically be found in legacy API libraries;
  • Child classes of other excluded classes.

The class-path.log file tells us that the archive was indeed used:

[0.005s][info][class,path] bootstrap loader class path=/Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home/lib/modules
[0.012s][info][class,path] Expecting BOOT path=/Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home/lib/modules
[0.012s][info][class,path] Expecting -Djava.class.path=
[0.012s][info][class,path] checking shared classpath entry: /Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home/lib/modules
[0.012s][info][class,path] ok
[0.125s][info][class,path] Expecting BOOT path=/Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home/lib/modules
[0.125s][info][class,path] Expecting -Djava.class.path=spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar
[0.125s][info][class,path] checking shared classpath entry: /Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home/lib/modules
[0.125s][info][class,path] ok
[0.125s][info][class,path] checking shared classpath entry: spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar
[0.125s][info][class,path] ok

How about the number of loaded classes?

Run the following command to see how many classes were loaded in total:

cat log/class-load.log | wc -l
16242

The same file contains data on files that were loaded from the shared archive. Run the following command to get the number: 

grep -o 'source: shared' -c log/class-load.log
14391

As you can see, about 88% of classes got into the archive.

What about the startup? The mean startup time of the application running without the archive was 3.3 seconds. In turn, the mean startup with the preloaded CDS archive was 1.9 seconds, which means that in this case, CDS yields 42% faster startup. Already great, but we can do even better with Spring AOT!

Create the CDS archive with Spring AOT enabled

Spring Ahead-of-time optimizations (AOT) are aimed primarily at leveraging the power of GraalVM Natime Image for Spring applications, but they can als help with startup reduction when using the standard JVM. I thought I should give it a try after reading CDS with Spring Framework 6.1 by Sébastien Deleuze. And I was amazed to see for myself how without extensive huffing and puffing with the configs you can get impressive performance boost!

Right, to the task at hand. Let’s first enable AOT processing in the pom.xml:

<plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <executions>
   <execution>
     <id>process-aot</id>
     <goals>
       <goal>process-aot</goal>
     </goals>
   </execution>
    </executions>
</plugin>

You can also enable AOT compilation without modifying the pom.xml. To do that, you need to run:

mvn clean compile spring-boot:process-aot package

Now, modify the command for generating the CDS archive in the following way:

mvn -Dmaven.test.skip=true clean package
java -Djarmode=tools -jar target/spring-petclinic-3.3.0-SNAPSHOT.jar extract
java -Dspring.aot.enabled=true -XX:ArchiveClassesAtExit=./application.jsa -Dspring.context.exit=onRefresh -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

After the archive has been created, run the application with:

java -Dspring.aot.enabled=true -XX:SharedArchiveFile=application.jsa -jar spring-petclinic-3.3.0-SNAPSHOT/spring-petclinic-3.3.0-SNAPSHOT.jar

In my case, Spring Petclinic started in 1.5 seconds, reflecting 54% startup time reduction!

Create the CDS archive using a container with CDS

In the tutorial below, we will make use of Docker multi-stage builds to create a CDS archive of a Spring Boot application and use it in a Docker container. We will use a Liberica Runtime Container with CDS (tagged with a cds tag).

Liberica Runtime Container is based on Liberica JDK, a Java runtime recommended by Spring, and Alpaquita Linux, a lightweight Linux distro tailor-made for Java. A natural choice for our experiment, I dare say.

If you want to compare startup times, you can first build a standard container image using the following Dockerfile:

FROM bellsoft/liberica-runtime-container:jdk-21-stream-musl as builder
RUN apk add wget

WORKDIR /home/app
ADD spring-petclinic-main /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package

FROM bellsoft/liberica-runtime-container:jdk-21-stream-musl

WORKDIR /home/app
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar petclinic.jar
CMD ["java", "-jar", "petclinic.jar"]

Here, we create a fat jar and run it the usual way.

Build the image with:

docker build -t petclinic-standard .

And then run it with:

docker run petclinic-standard

In my case, it took 5.3 seconds for the containerized application to start.

Now, let’s make use of CDS and AOT.

To create the archive in a Docker container, you need the following Dockerfile:

FROM bellsoft/liberica-runtime-container:jdk-21-cds-musl as builder

WORKDIR /home/app
ADD spring-petclinic /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package

FROM bellsoft/liberica-runtime-container:jdk-21-cds-slim-musl as optimizer

WORKDIR /app
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted

FROM bellsoft/liberica-runtime-container:jdk-21-cds-slim-musl

ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "-XX:SharedArchiveFile=application.jsa", "-jar", "/app/app.jar"]

WORKDIR /app
COPY --from=optimizer /app/extracted/dependencies/ ./
COPY --from=optimizer /app/extracted/spring-boot-loader/ ./
COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./
COPY --from=optimizer /app/extracted/application/ ./

RUN java -Dspring.aot.enabled=true -XX:ArchiveClassesAtExit=./application.jsa -Dspring.context.exit=onRefresh -jar /app/app.jar

Here, we have tree build stages, each of which uses the base image with Liberica JDK and CDS.

During the first stage, we, we build the usual fat jar file.

During the second stage, we make use of Spring Boot -Djarmode=tools to extract layers from our application. Running a layered jar in a containerized environment reduces overhead. In addition, the layering gives the developers more fine-grained control over the image updates. The most frequently updated layers are placed on top of the image, so in most cases, Docker only needs to update these top layers and pull other layers from the cache.

During the third stage, we transfer extracted layers into a fresh image. We also run the application with the options required for creating a CDS archive. The entrypoint contains the -XX:SharedArchiveFile=application.jsa option for using the archive. 

Run the standard command for building a Docker image:

docker build -t cdsimage .

After that, you can run the container image of the application the usual way:

docker run cdsimage
              |\      _,,,--,,_
             /,`.-'`'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'-'__     ___ __    _ ___ _______
 |       | '---''(_/._)-'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/
:: Built with Spring Boot :: 3.4.2
2025-05-02T07:14:23.144Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Starting AOT-processed PetClinicApplication v3.4.0-SNAPSHOT using Java 21.0.7 with PID 1 (/app/app.jar started by root in /app)
2025-05-02T07:14:23.146Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : No active profile set, falling back to 1 default profile: "default"
2025-05-02T07:14:23.480Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-05-02T07:14:23.485Z  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-05-02T07:14:23.485Z  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.34]
2025-05-02T07:14:23.496Z  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-05-02T07:14:23.497Z  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 349 ms
2025-05-02T07:14:23.661Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2025-05-02T07:14:23.683Z  INFO 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:3df44d9e-60d4-486d-9206-d135fc413101 user=SA
2025-05-02T07:14:23.684Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2025-05-02T07:14:23.741Z  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-05-02T07:14:23.748Z  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.6.5.Final
2025-05-02T07:14:23.753Z  INFO 1 --- [           main] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2025-05-02T07:14:23.792Z  INFO 1 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-05-02T07:14:23.802Z  INFO 1 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001005: Database info:
	Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
	Database driver: undefined/unknown
	Database version: 2.3.232
	Autocommit mode: undefined/unknown
	Isolation level: undefined/unknown
	Minimum pool size: undefined/unknown
	Maximum pool size: undefined/unknown
2025-05-02T07:14:24.012Z  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-05-02T07:14:24.013Z  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-05-02T07:14:24.121Z  INFO 1 --- [           main] o.s.d.j.r.query.QueryEnhancerFactory     : Hibernate is in classpath; If applicable, HQL parser will be used.
2025-05-02T07:14:24.734Z  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoints beneath base path '/actuator'
2025-05-02T07:14:24.785Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-05-02T07:14:24.788Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Started PetClinicApplication in 1.886 seconds (process running for 2.122)

As a result, we achieved startup reduction by 60%!

You can also run multiple containers based on this image. This way, they will share the same CDS archive, which may contribute to reduced memory consumption in the cloud.

Use CDS with buildpacks

Great news for Spring developers using buildpacks (and a good incentive to try them out for those who prefer traditional Dockerfiles): Paketo buildpacks support the creation of a CDS archive!

Make sure that you have pack installed, and Paketo Base builder is the default builder (more on containerizing Java applications with buildpacks here).

To make use of CDS and Spring AOT with buildpacks, run the following command:

pack build petclinic-cds-pack --env BP_JVM_VERSION=21 --env BP_SPRING_AOT_ENABLED=true --env BP_JVM_CDS_ENABLED=true

Alternatively, you can enable CDS and AOT directly in the Maven / Gradle plugin:

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <env>
                            <BP_SPRING_AOT_ENABLED>true</BP_SPRING_AOT_ENABLED>
                            <BP_JVM_CDS_ENABLED>true</BP_JVM_CDS_ENABLED>
                        </env>
                    </image>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>process-aot</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
plugins {
  java
  id("org.graalvm.buildtools.native") version "0.10.3"
}

tasks.named<BootBuildImage>("bootBuildImage") {
	environment.putAll(mapOf(
		"BP_SPRING_AOT_ENABLED" to "true",
		"BP_JVM_CDS_ENABLED" to "true"
	))
}

And then, build the container image with the usual command: mvn spring-boot:build-image for Maven or gradle bootBuildImage for Gradle.

How to Use AOT Cache instead of AppCDS with Java 24 for even faster startup

Java 24 came with JEP 483: Ahead-of-Time Class Loading & Linking, which is the first step towards integrating Project Leyden in the mainline OpenJDK. JEP 483 builds in AppCDS, but goes further and creates an AOT Cache where the classes are iniialized, loaded, and linked. This way, the JVM has even less work to do upon application start.

Great news that Spring boot supports both CDS and AOT Cache. What is more, it is recommended to use AOT Cache instead of CDS if you have migrated to JDK 24.

You can use AOT Cache with or without Spring AOT. Spring AOT enables more startup improvements, but it freezes Spring profiles, which may require extra attention from you.

If, however, you want to use Spring AOT, make sure that you enabled the process-aot goal in the Maven/Gradle plugin.

Enough talking, Let's see how we can use AOT Cache with Docker container images! You will need the following Dockerfile (remember that Spring AOT is optional):

FROM bellsoft/liberica-runtime-container:jdk-24-stream-musl as builder

WORKDIR /home/app
ADD spring-petclinic-main /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package

FROM bellsoft/liberica-runtime-container:jdk-24-cds-slim-musl as optimizer

WORKDIR /app
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar app.jar
RUN java -Djarmode=tools -jar app.jar extract --layers --destination extracted


FROM bellsoft/liberica-runtime-container:jdk-24-cds-slim-musl

WORKDIR /app

ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "-XX:AOTCache=app.aot", "-jar", "/app/app.jar"]

COPY --from=optimizer /app/extracted/dependencies/ ./
COPY --from=optimizer /app/extracted/spring-boot-loader/ ./
COPY --from=optimizer /app/extracted/snapshot-dependencies/ ./
COPY --from=optimizer /app/extracted/application/ ./

RUN java -Dspring.aot.enabled=true -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh -jar /app/app.jar
RUN java -Dspring.aot.enabled=true -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar /app/app.jar

Note the commands for using AOT Cache:

RUN java -Dspring.aot.enabled=true -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -Dspring.context.exit=onRefresh -jar /app/app.jar
RUN java -Dspring.aot.enabled=true -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar /app/app.jar

With the first command, we perform the trial run and record the AOT configuration into the app.aotconf file. With the second command, we use this file to create the cache (app.aot). In the ENTRYPOINT, we specify the path to the cache file so that JVM could use it.

Build the contaiiner image and run it:

docker run --rm -p 8081:8081 petclinic-aot-cache
              |\      _,,,--,,_
             /,`.-'`'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'-'__     ___ __    _ ___ _______
 |       | '---''(_/._)-'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/
:: Built with Spring Boot :: 3.4.2
2025-05-02T12:00:11.338Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Starting AOT-processed PetClinicApplication v3.4.0-SNAPSHOT using Java 24.0.1 with PID 1 (/app/app.jar started by root in /app)
2025-05-02T12:00:11.339Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : No active profile set, falling back to 1 default profile: "default"
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by org.apache.tomcat.jni.Library in an unnamed module (file:/app/lib/tomcat-embed-core-10.1.34.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
2025-05-02T12:00:11.657Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-05-02T12:00:11.663Z  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-05-02T12:00:11.663Z  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.34]
2025-05-02T12:00:11.681Z  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-05-02T12:00:11.681Z  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 342 ms
2025-05-02T12:00:11.810Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2025-05-02T12:00:11.829Z  INFO 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:c2ce2ed4-50c4-4182-8d9d-80102a5a5628 user=SA
2025-05-02T12:00:11.829Z  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2025-05-02T12:00:11.881Z  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-05-02T12:00:11.885Z  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.6.5.Final
2025-05-02T12:00:11.886Z  INFO 1 --- [           main] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2025-05-02T12:00:11.917Z  INFO 1 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-05-02T12:00:11.921Z  INFO 1 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001005: Database info:
	Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
	Database driver: undefined/unknown
	Database version: 2.3.232
	Autocommit mode: undefined/unknown
	Isolation level: undefined/unknown
	Minimum pool size: undefined/unknown
	Maximum pool size: undefined/unknown
2025-05-02T12:00:12.069Z  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-05-02T12:00:12.069Z  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-05-02T12:00:12.165Z  INFO 1 --- [           main] o.s.d.j.r.query.QueryEnhancerFactory     : Hibernate is in classpath; If applicable, HQL parser will be used.
2025-05-02T12:00:12.690Z  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoints beneath base path '/actuator'
2025-05-02T12:00:12.732Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-05-02T12:00:12.736Z  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Started PetClinicApplication in 1.488 seconds (process running for 1.776)

The application starts 67% faster!

Summary

We discussed three ways of using CDS with Spring Boot. You can:

  • Create the archive and run the app on the local machine,
  • Use a Dockerfile,
  • Use a buildpack.

To sum up, CDS is effortless to use and doesn’t require code refactoring. At the same time, it allows for a good startup improvement, up to 54%, depending on the configuration. This article analyzes only one application, so the results can vary depending on the program under evaluation. 

Another claimed advantage of CDS is reduced memory footprint when the archive is shared among multiple VM instances. If you’d like to measure footprint reduction, there’s a great article by Volker Simonis that gives detailed instructions on running the experiment, gathering the statistics, and evaluating the data.

 

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading